package org.example.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.example.system.common.model.Option;
import org.example.system.converter.DictConverter;
import org.example.system.mapper.SysDictMapper;
import org.example.system.model.entity.SysDict;
import org.example.system.model.form.DictForm;
import org.example.system.model.query.DictPageQuery;
import org.example.system.model.vo.DictPageVO;
import org.example.system.service.SysDictService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 数据字典项业务实现类
 *
 * @author LY
 */
@Service
@RequiredArgsConstructor
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {
    private final DictConverter dictConverter;

    /**
     * 获取字典数据项的分页列表
     * 该方法根据传入的查询参数，构建查询条件并执行分页查询，最后将查询结果转换为分页视图对象返回。
     *
     * @param queryParams 包含分页和查询条件的参数对象，包括页码、每页大小、关键词和类型编码等
     * @return 包含字典数据项的分页视图对象，用于前端展示
     */
    @Override
    public Page<DictPageVO> getDictPage(DictPageQuery queryParams) {
        int pageNum = Math.max(1, queryParams.getPageNum());
        int pageSize = Math.max(1, queryParams.getPageSize());

        String keywords = queryParams.getKeywords();
        String typeCode = queryParams.getTypeCode();

        // 构建查询条件
        LambdaQueryWrapper<SysDict> queryWrapper = new LambdaQueryWrapper<>();
        boolean hasKeywords = StrUtil.isNotBlank(keywords);
        if (hasKeywords) {
            queryWrapper.like(SysDict::getName, keywords);
        }
        boolean hasTypeCode = StrUtil.isNotBlank(typeCode);
        if (hasTypeCode) {
            queryWrapper.eq(SysDict::getTypeCode, typeCode);
        }

        // 添加字段选择：仅查询需要的字段，减少数据传输量
        queryWrapper.select(SysDict::getId, SysDict::getName, SysDict::getValue, SysDict::getStatus);

        // 执行分页查询
        Page<SysDict> dictItemPage = this.page(new Page<>(pageNum, pageSize), queryWrapper);

        // 实体转换
        return dictConverter.entity2Page(dictItemPage);
    }

    /**
     * 根据字典数据项ID获取字典数据项的表单详情。
     * 该方法首先对传入的ID进行非空校验，若为空则抛出异常。然后根据ID查询对应的字典数据项实体，
     * 若实体不存在则抛出异常。最后将查询到的实体转换为表单对象并返回。
     *
     * @param id 字典数据项ID，不能为空
     * @return 字典数据项的表单对象
     * @throws IllegalArgumentException 如果ID为空或字典数据项不存在
     */
    @Override
    public DictForm getDictForm(Long id) {
        // 参数校验
        if (id == null) {
            throw new IllegalArgumentException("字典数据项ID不能为空");
        }

        // 根据ID查询字典数据项实体
        SysDict entity = this.getOne(buildDictQueryWrapper(id));
        if (entity == null) {
            throw new IllegalArgumentException("字典数据项不存在");
        }

        // 实体转换
        return dictConverter.entity2Form(entity);
    }

    /**
     * 构建用于查询字典数据项的查询条件
     *
     * @param id 字典数据项的唯一标识符，用于查询指定字典数据项
     * @return LambdaQueryWrapper<SysDict> 返回一个LambdaQueryWrapper对象，包含查询条件和选择的字段
     */
    private LambdaQueryWrapper<SysDict> buildDictQueryWrapper(Long id) {
        // 创建LambdaQueryWrapper对象，并设置查询条件和选择的字段
        return new LambdaQueryWrapper<SysDict>()
                .eq(SysDict::getId, id)
                .select(
                        SysDict::getId,
                        SysDict::getTypeCode,
                        SysDict::getName,
                        SysDict::getValue,
                        SysDict::getStatus,
                        SysDict::getSort,
                        SysDict::getRemark
                );
    }

    /**
     * 新增字典数据项
     * 该方法用于将传入的字典数据项表单转换为实体对象，并将其持久化到数据库中。
     *
     * @param dictForm 字典数据项表单，包含需要新增的字典数据项信息
     * @return 是否新增成功，true表示新增成功，false表示新增失败
     */
    @Override
    public boolean saveDict(DictForm dictForm) {
        // 将字典数据项表单转换为实体对象
        SysDict entity = dictConverter.form2Entity(dictForm);
        // 将实体对象持久化到数据库，并返回操作结果
        return this.save(entity);
    }

    /**
     * 修改字典数据项
     * 该方法用于根据提供的字典数据项ID和字典数据项表单，更新对应的字典数据项。
     * 首先将字典数据项表单转换为实体对象，然后调用更新方法进行更新操作。
     *
     * @param id       字典数据项ID，用于指定需要更新的字典数据项
     * @param dictForm 字典数据项表单，包含需要更新的字典数据项信息
     * @return 是否修改成功，true表示修改成功，false表示修改失败
     */
    @Override
    public boolean updateDict(Long id, DictForm dictForm) {
        // 将字典数据项表单转换为实体对象
        SysDict entity = dictConverter.form2Entity(dictForm);
        // 调用更新方法，根据实体对象更新字典数据项
        return this.updateById(entity);
    }

    /**
     * 删除字典数据项
     *
     * @param idsStr 字典数据项ID，多个以英文逗号(,)分割的字符串
     * @return 是否删除成功，true表示删除成功，false表示删除失败
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteDict(String idsStr) {
        Assert.isTrue(StrUtil.isNotBlank(idsStr), "删除数据为空");

        // 将逗号分隔的ID字符串解析为Long类型的列表
        List<Long> ids = parseIdsString(idsStr);

        // 根据ID列表删除对应的字典数据项，并返回删除结果
        return this.removeByIds(ids);
    }

    /**
     * 将输入的ID字符串解析为Long类型的列表，并过滤非法输入。
     * 该函数首先将输入的字符串按逗号分隔，然后去除每个子字符串的前后空格，
     * 接着过滤掉空字符串和非数字字符串，最后将合法的字符串转换为Long类型并收集到列表中。
     *
     * @param idsStr 输入的ID字符串，多个ID之间用逗号分隔
     * @return 解析后的ID列表，包含所有合法的Long类型ID
     * @throws IllegalArgumentException 如果输入的字符串中包含无法解析为Long类型的非法ID
     */
    private List<Long> parseIdsString(String idsStr) {
        // 将输入的字符串按逗号分隔，并去除每个子字符串的前后空格
        return Arrays.stream(idsStr.trim().split(","))
                .map(String::trim)
                // 过滤掉空字符串和非数字字符串
                .filter(s -> StrUtil.isNotEmpty(s) && StrUtil.isNumeric(s))
                // 将合法的字符串转换为Long类型
                .map(Long::valueOf)
                // 收集所有合法的Long类型ID到列表中
                .collect(Collectors.toList());
    }

    /**
     * 根据字典类型编码获取字典下拉列表选项。
     * 该方法首先对传入的字典类型编码进行校验，如果为空则返回空列表。
     * 然后根据类型编码查询对应的字典项，并将查询结果转换为下拉列表选项格式。
     *
     * @param typeCode 字典类型编码，用于指定需要查询的字典类型
     * @return 返回一个包含字典下拉列表选项的List，如果类型编码为空或未找到对应字典项，则返回空列表
     */
    @Override
    public List<Option<String>> listDictOptions(String typeCode) {
        // 参数校验
        if (StrUtil.isBlank(typeCode)) {
            return Collections.emptyList();
        }

        // 根据类型编码查询字典项，仅选择字典的值和名称字段
        List<SysDict> dictList = this.list(new LambdaQueryWrapper<SysDict>()
                .eq(SysDict::getTypeCode, typeCode)
                .select(SysDict::getValue, SysDict::getName));

        // 将查询到的字典项转换为下拉列表选项，并返回结果
        return CollectionUtil.emptyIfNull(dictList)
                .stream()
                .map(this::convertToOption)
                .collect(Collectors.toList());
    }

    /**
     * 将 SysDict 对象转换为 Option 对象。
     *
     * @param dictItem 需要转换的 SysDict 对象，包含字典项的值和名称。
     * @return 返回一个新的 Option 对象，其值来自 SysDict 的 value 属性，名称来自 SysDict 的 name 属性。
     */
    private Option<String> convertToOption(SysDict dictItem) {
        return new Option<>(dictItem.getValue(), dictItem.getName());
    }
}
